﻿using System;
using System.Security.Cryptography;
using EncryptionLib.Util;
using System.Xml.Serialization;

namespace EncryptionLib.Users
{
    public class UserData
    {
        public string Email
        {
            get;
            set;
        }
        public string Name
        {
            get;
            set;
        }
        public DateTime Birthday
        {
            get;
            set;
        }
        public string PhoneNumber
        {
            get;
            set;
        }
        public string Address
        {
            get;
            set;
        }

        public PassphraseBase Passphrase
        {
            get
            {
                return m_Passphrase;
            }
            set
            {
                if(m_Passphrase == null)
                {
                    m_Passphrase = value;
                }
                else
                {
                    // get the current private key
                    byte[] arrPrivateKey = null;
                    int iLen = SymmetricAlgo.GetDESKeyLen();
                    byte[] arrSymPassword = null, arrSymIV = null;
                    if (m_Passphrase.GetSecretKeys(iLen, ref arrSymPassword, ref arrSymIV))
                    {
                        // and we got the plain private key.
                        arrPrivateKey = SymmetricAlgo.DESDecrypt(PrivateKey,
                            arrSymPassword, arrSymIV, CipherMode.CFB, PaddingMode.PKCS7);
                    }

                    m_Passphrase = value;

                    // encrypt private key with the new passphrase
                    if (arrPrivateKey != null
                        && m_Passphrase.GetSecretKeys(iLen, ref arrSymPassword, ref arrSymIV))
                    {
                        // and we got the encrypted private key.
                        m_PrivateKey = SymmetricAlgo.DESEncrypt(arrPrivateKey,
                            arrSymPassword, arrSymIV, CipherMode.CFB, PaddingMode.PKCS7);
                    }
                }
            }
        }
        private PassphraseBase m_Passphrase = null;

        public string PublicKey
        {
            get
            {
                return m_sPublicKey;
            }
            set
            {
                if(string.IsNullOrEmpty(m_sPublicKey))
                {
                    m_sPublicKey = value;
                }
                else
                {
                    throw new ArgumentException("Cannot set another Public key. Use GenerateKeys() to get a new key pair.");
                }
            }
        }
        private string m_sPublicKey = string.Empty;

        /// <summary>
        /// Encrypted privateKey.
        /// </summary>
        public byte[] PrivateKey
        {
            get
            {
                return m_PrivateKey;
            }
            set
            {
                if(m_PrivateKey == null)
                {
                    m_PrivateKey = value;
                }
                else
                {
                    throw new ArgumentException("Cannot set another private key. Use GenerateKeys() to get a new key pair.");
                }
            }
        }
        private byte[] m_PrivateKey = null;

        /// <summary>
        /// Length of the RSA key pair.
        /// </summary>
        public int SecretKeyLength
        {
            get
            {
                return m_iSecretKeyLen;
            }
            set
            {
                if(m_iSecretKeyLen == -1)
                    m_iSecretKeyLen = value;
                else
                    throw new ArgumentException("Cannot set Secret key length. Construct another UserData object!");
            }
        }
        private int m_iSecretKeyLen = -1;

        public string GetPlainPrivateKey(byte[] sPassphrase)
        {
            if(Passphrase.Verify(sPassphrase))
            {
                byte[] arrPassword = null, arrIV = null;
                Passphrase.GetSecretKeys(SymmetricAlgo.GetDESKeyLen(), ref arrPassword, ref arrIV);

                byte[] arrPrivateKey = SymmetricAlgo.DESDecrypt(PrivateKey, arrPassword, arrIV,
                CipherMode.CFB, PaddingMode.PKCS7);
                return SerializerHelper.Bytes2String(arrPrivateKey);
            }
            return string.Empty;
        }

        public UserData(){}

        public UserData(string sEmail, string sName,
            DateTime dtBirth, string sPhoneNum,
            string sAddress, string passPhrase, int iKeyLen)

            : this(sEmail, sName, dtBirth, sPhoneNum, 
            sAddress, SerializerHelper.String2Bytes(passPhrase), iKeyLen)
        {
        }

        public UserData(string sEmail, string sName, 
            DateTime dtBirth, string sPhoneNum, 
            string sAddress, byte[] passPhrase, int iKeyLen)
        {
            Email = sEmail;
            Name = sName;
            Birthday = dtBirth;
            PhoneNumber = sPhoneNum;
            Address = sAddress;
            this.Passphrase = new Passphrase(passPhrase);

            SecretKeyLength = iKeyLen;
            GenerateKeys();
        }

        /// <summary>
        /// Generate a new key pair.
        /// </summary>
        private void GenerateKeys()
        {
            if (SecretKeyLength < 512 || SecretKeyLength > 1024 || (SecretKeyLength % 64 != 0))
                throw new ArgumentOutOfRangeException("Key length must be in [512, 1024].");

            RSACryptoServiceProvider m_rsaCryptor = new RSACryptoServiceProvider(SecretKeyLength);
            PublicKey = m_rsaCryptor.ToXmlString(false);

            // the real private key is here...
            string sPrivateKey = m_rsaCryptor.ToXmlString(true);

            // but we are gonna encrypt it...
            int iLen = SymmetricAlgo.GetDESKeyLen();
            byte[] arrSymPassword = null, arrSymIV = null;
            if (Passphrase.GetSecretKeys(iLen, ref arrSymPassword, ref arrSymIV))
            {
                // and we got the encrypted private key.
                PrivateKey = SymmetricAlgo.DESEncrypt(SerializerHelper.String2Bytes(sPrivateKey),
                    arrSymPassword, arrSymIV, CipherMode.CFB, PaddingMode.PKCS7);
            }
        }

        public override string ToString()
        {
            return Email;
        }

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public static bool operator==(UserData u1, UserData u2)
        {
            if ((object)u1 == null && (object)u2 == null)
            {
                return true;
            }
            else if ((object)u1 != null && (object)u2 != null)
            {
                return (string.Compare(u1.Email, u2.Email) == 0);
            }
            else
                return false;
        }

        public static bool operator !=(UserData u1, UserData u2)
        {
            return !(u1 == u2);
        }
    }
}
